package com.taotao.util;

import com.google.common.base.Charsets;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.ThreadLocalRandom;
import java.util.stream.Collectors;

/**
 * 工具类
 *
 * @author sunch
 * @since 2020-03-23
 */
public final class CommonUtil {
    /**
     * 计算年龄
     */
    public static int calculateAge(Date birthday) {
        LocalDateTime birthdayTime = birthday.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
        return LocalDateTime.now().getYear() - birthdayTime.getYear();
    }

    /**
     * 表序号
     */
    public static int buildTableNum(long uid) {
        return (int) (uid & 3);
    }

    /**
     * UTC转GMT8
     */
    public static Date convertUTC2GMT8(Date date) {
        return new Date(date.getTime() + 8*3600*1000);
    }

    /**
     * 计算新日期
     */
    public static Date addSomeDays(Date baseDate, int days, int hour) {
        Calendar cal = Calendar.getInstance();
        cal.setTime(baseDate);
        cal.add(Calendar.DAY_OF_YEAR, days);
        cal.set(Calendar.HOUR_OF_DAY, hour);
        cal.set(Calendar.MINUTE, 0);
        cal.set(Calendar.SECOND, 0);
        cal.set(Calendar.MILLISECOND, 0);

        return cal.getTime();
    }

    /**
     * 今天开始时间(北京时间)
     */
    public static Date getTodayStartTime(Date nowUtc) {
        Date nowGMT8 = convertUTC2GMT8(nowUtc);
        return addSomeDays(nowGMT8, 0, -8);
    }

    /**
     * 今天结束时间(北京时间)
     */
    public static Date getTodayEndTime(Date nowUtc) {
        Date nowGMT8 = convertUTC2GMT8(nowUtc);
        return addSomeDays(nowGMT8, 1, -8);
    }

    /**
     * 三方支付加签
     */
    public static String doEncryptSign(Map<String, Object> map, String appKey) {
        Object[] keys = map.keySet().toArray();
        Arrays.sort(keys);
        StringBuilder originStr = new StringBuilder();
        for (Object key : keys) {
            Object value = map.get(key.toString());
            if (Objects.nonNull(value) && StringUtils.isNotBlank(value.toString())) {
                if ("sign".equals(key.toString().trim())) {
                    continue;
                }

                originStr.append(key).append("=").append(value).append("&");
            }
        }
        originStr.append("key=").append(appKey);
        return DigestUtils.sha256Hex(originStr.toString());
    }

    /**
     * 三方支付验签
     */
    public static boolean doVerifySign(Map<String, Object> paraMap, String sign, String appKey) {
        return StringUtils.equals(sign, doEncryptSign(paraMap, appKey));
    }

    /**
     * 获取本机ip地址
     */
    public static String getLocalIpAddr() {
        Enumeration<NetworkInterface> networks;
        try {
            networks = NetworkInterface.getNetworkInterfaces();
        } catch (SocketException e) {
            return null;
        }

        while (networks.hasMoreElements()){
            Enumeration<InetAddress> addrs = networks.nextElement().getInetAddresses();
            while (addrs.hasMoreElements()){
                InetAddress ip = addrs.nextElement();
                if (ip != null && ip.isSiteLocalAddress()){
                    if (StringUtils.isEmpty(ip.getHostAddress())){
                        return null;
                    }
                    return ip.getHostAddress();
                }
            }
        }
        return null;
    }

    /**
     * 金额规整
     */
    public static long normalizeAmount(long totalCost) {
        return (long) Math.ceil(totalCost / 100.0);
    }

    /**
     * 版本比较
     */
    public static int compareVersion(String targetVersion, String baseVersion) {
        if (StringUtils.isEmpty(targetVersion) || StringUtils.isEmpty(baseVersion)) {
            return 0;
        }

        String[] baseArray = baseVersion.split("\\.");
        String[] targetArray = targetVersion.split("\\.");
        int minLength = Math.min(baseArray.length, targetArray.length);

        for (int i = 0; i < minLength; i++) {
            if (Integer.parseInt(targetArray[i]) != Integer.parseInt(baseArray[i])) {
                int radix = (i == 0) ? 100 : (i == 1) ? 10 : 1;
                return radix * (Integer.parseInt(targetArray[i]) - Integer.parseInt(baseArray[i]));
            }
        }

        return 0;
    }

    /**
     * 统计字符串数字个数
     */
    public static int statNumericCount(String src) {
        int count = 0;
        for (int i = 0; i < src.length(); i++) {
            if (src.charAt(i) >= 48 && src.charAt(i) <= 57) {
                count++;
            }
        }

        return count;
    }

    /**
     * 提取视频封面
     */
    public static String extractVideoCover(String videoUrl, int timeMs) {
        return String.format("%s?x-oss-process=video/snapshot,t_%d,f_jpg,w_0,h_0,m_fast", videoUrl, timeMs);
    }

    /**
     * url二次转换
     */
    public static String convertResUrl(String resUrl, int platform) {
        if (resUrl.contains("v.folo.cool")) {
            return resUrl;
        }

        int pos = resUrl.lastIndexOf("/");
        return resUrl.substring(0, pos) + "/" + platform + resUrl.substring(pos);
    }


    /**
     * AES加密
     */
    public static String doAESEncrypt(String content, String aesKey) {
        SecretKeySpec keySpec = new SecretKeySpec(aesKey.getBytes(Charsets.UTF_8), "AES");
        try {
            final Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
            cipher.init(Cipher.ENCRYPT_MODE, keySpec);
            byte[] encrypted = cipher.doFinal(content.getBytes());
            return Base64.encodeBase64String(encrypted);
        } catch (Throwable e) {
            e.printStackTrace();
        }

        return null;
    }

    /**
     * AES解密
     */
    public static String doAESDecrypt(String content, String aesKey) {
        SecretKeySpec keySpec = new SecretKeySpec(aesKey.getBytes(Charsets.UTF_8), "AES");
        try {
            final Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
            cipher.init(Cipher.DECRYPT_MODE, keySpec);
            return new String(cipher.doFinal(Base64.decodeBase64(content)), Charsets.UTF_8);
        } catch (Throwable e) {
            e.printStackTrace();
        }

        return null;
    }


    public static String date2String(Date date, String pattern) {
        SimpleDateFormat format = new SimpleDateFormat(pattern);
        return format.format(date);
    }

    /**
     * 数值范围 1 到 7
     */
    public static int getDayOfWeek(String strLocalTime) {
        LocalDate localDate = LocalDate.parse(strLocalTime, DateTimeFormatter.ofPattern("yyyyMMddHHmm"));
        return localDate.getDayOfWeek().getValue();
    }

    /**
     * 生成墨西哥支付 CURP
     * 18位: 任意一个字母 + 一个元音 + 任意两个字母 + 任意两个数字(年份后2) + 12以内的两个数字(个位以0开始, 月份) + 28以内的两个数字（个位以0开始, 日）+ H/M(性别) + 墨西哥州缩写 + 三个辅音字母 + 任意两个数字
     */
    public static String generateMexicoCURP() {
        List<String> vowels = Arrays.asList("A", "E", "I", "O", "U");
        List<String> consonants = Arrays.asList("B", "C", "D", "F", "G", "H", "J", "K", "L", "M", "P", "Q", "R", "S", "T", "V", "W", "X", "Y", "Z");
        List<String> allLetters = Arrays.asList("A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z");
        List<String> mexicoStates = Arrays.asList(
                "AG", "BC", "BS", "CM", "CS", "CH", "CO", "CL", "DG", "GT", "GR", "HG", "JC", "EM", "CX", "MI", "MO", "NA", "NL",
                "OA", "PU", "QT", "QR", "SL", "SI", "SO", "TB", "TM", "TL", "VE", "YU", "ZA"
        );

        StringBuilder curp = new StringBuilder();
        curp.append(allLetters.get(ThreadLocalRandom.current().nextInt(0, allLetters.size())))
                .append(vowels.get(ThreadLocalRandom.current().nextInt(0, vowels.size())))
                .append(RandomStringUtils.randomAlphabetic(2))
                .append(ThreadLocalRandom.current().nextInt(60, 99))
                .append(String.format("%02d", ThreadLocalRandom.current().nextInt(1, 12)))
                .append(String.format("%02d", ThreadLocalRandom.current().nextInt(1, 28)))
                .append((ThreadLocalRandom.current().nextInt(1, 100) % 2 == 0 ? "H" : "M"))
                .append(mexicoStates.get(ThreadLocalRandom.current().nextInt(0, mexicoStates.size())))
                .append(consonants.get(ThreadLocalRandom.current().nextInt(0, consonants.size())))
                .append(consonants.get(ThreadLocalRandom.current().nextInt(0, consonants.size())))
                .append(consonants.get(ThreadLocalRandom.current().nextInt(0, consonants.size())))
                .append(String.format("%02d", ThreadLocalRandom.current().nextInt(1, 99)));
        return curp.toString().toUpperCase().replace("N", "X");
    }

    /**
     * 生成哥伦比亚支付 CC
     * 10位: "10" + 8位数字
     */
    public static String generateColumbiaCC() {
        return "10" + String.format("%08d", ThreadLocalRandom.current().nextInt(10000000, 100000000));
    }

    /**
     * 生成阿根廷支付 DNI
     * 8位数字
     */
    public static String generateArgentinaDNI() {
        final List<String> strNumbers = Arrays.asList("1", "2", "3", "4", "5", "6", "7", "8", "9");
        Collections.shuffle(strNumbers);

        return strNumbers.stream().limit(8).collect(Collectors.joining());
    }

    /**
     * 生成秘鲁支付 DNI
     * 9位数字
     */
    public static String generatePeruDNI() {
        return String.format("%09d", ThreadLocalRandom.current().nextInt(100000000, 1000000000));
    }

    /**
     * 生成巴西支付 CEP
     * 5位数字+"-"+3位数字
     */
    public static String generateBrazilCEP() {
        final List<String> codeCEP12 = Arrays.asList(
                "20", "45", "64", "73", "28", "39", "33", "52", "22", "84", "96", "50", "19", "93", "37", "16", "70", "87",
                "81", "94", "91", "65", "77", "71", "55", "32", "15", "57", "30", "31", "68", "36", "27", "53", "66", "11",
                "85", "86", "83", "75", "24", "23", "17", "21", "58", "54", "07", "02", "05", "25", "03", "26", "79", "59",
                "29", "13", "78", "40", "89", "38", "14", "69", "60", "09", "41", "04", "18", "35", "88", "06", "08", "72", "12", "74");

        final List<String> codeCEP34 = Arrays.asList(
                "78", "92", "65", "43", "76", "74", "24", "62", "66", "45", "26", "44", "63", "53", "86", "87", "57", "75",
                "54", "73", "72", "47", "94", "52", "61", "93", "42", "84", "82", "35", "91", "12", "34", "15", "85", "71",
                "27", "25", "16", "51", "33", "23", "13", "18", "14", "22", "41", "17", "10", "32", "81", "20", "60", "31",
                "03", "04", "05", "11", "21", "09", "50", "90", "40", "30", "80", "08", "70", "01", "07", "02", "06");

        final List<String> codeCEP678 = Arrays.asList(
                "011", "315", "185", "305", "225", "175", "830", "165", "155", "145", "215", "135", "095", "820", "085", "125",
                "205", "002", "075", "065", "115", "810", "105", "055", "790", "045", "035", "780", "800", "770", "025", "760",
                "015", "750", "740", "001", "005", "730", "720", "690", "710", "680", "700", "670", "660", "650", "640", "630",
                "590", "620", "610", "580", "570", "600", "560", "550", "540", "490", "530", "480", "470", "520", "510", "460",
                "140", "130", "120", "110", "090", "100", "080", "070", "060", "050", "040", "030", "020", "010", "000", "440",
                "450", "500", "430", "390", "420", "380", "410", "370", "360", "340", "400", "350", "290", "330", "320", "280",
                "310", "270", "260", "300", "250", "240", "230", "190", "220", "210", "180", "170", "200", "160", "150");

        String strCode12 = codeCEP12.get(ThreadLocalRandom.current().nextInt(codeCEP12.size()));
        String strCode34 = codeCEP34.get(ThreadLocalRandom.current().nextInt(codeCEP34.size()));
        String strCode5 = String.valueOf(ThreadLocalRandom.current().nextInt(0, 7));
        String strCode678 = codeCEP678.get(ThreadLocalRandom.current().nextInt(codeCEP678.size()));
        return strCode12 + strCode34 + strCode5 + "-" + strCode678;
    }

    /**
     * 巴西支付 CPF
     */
    public static String generateBrazilCPF() {
        Random random = new Random();

        final int c = 10;
        long d = random.nextInt(c);
        long e = random.nextInt(c);
        long f = random.nextInt(c);
        long g = random.nextInt(c);
        long h = random.nextInt(c);
        long i = random.nextInt(c);
        long j = random.nextInt(c);
        long k = random.nextInt(c);
        long l = random.nextInt(c);
        long m = 2 * l + 3 * k + 4 * j + 5 * i + 6 * h + 7 * g + 8 * f + 9 * e + 10 * d;

        m = 11 - m % 11;
        if (m >= 10) {
            m = 0;
        }

        long n = 2 * m + 3 * l + 4 * k + 5 * j + 6 * i + 7 * h + 8 * g + 9 * f + 10 * e + 11 * d;
        n = 11 - n % 11;

        if (n >= 10) {
            n = 0;
        }

        String cpf = "" + d + e + f + "." + g + h + i + "." + j + k + l + "-" + m + n;
        return cpf.replace(".", "").replace("-", "");
    }

    /**
     * 生成埃及id 14位
     * 1位世纪+6位出生日期(YYMMDD)+2位地区码+4位序列号+1位校验码
     */
    public static String generateEgyptID() {
        // 2表示1900-1999, 3表示2000-2099
        String century = "2";

        // 6位出生日期YYMMDD
        int year = 70 + ThreadLocalRandom.current().nextInt(0, 30); // 1970-1999 简化为 70-99
        int month = ThreadLocalRandom.current().nextInt(1, 13);
        int day = ThreadLocalRandom.current().nextInt(1, 28); // 简化日期范围
        String birthDate = String.format("%02d%02d%02d", year, month, day);

        // 2位地区码
        List<String> governorateList = Arrays.asList("01", "02", "03", "04", "11", "12", "13", "14", "15", "16", "17", "18",
                "19", "21", "22", "23", "24", "25", "26", "27", "28", "29", "31", "32", "33", "34", "35", "88");
        String governorate = governorateList.get(ThreadLocalRandom.current().nextInt(0, governorateList.size()));

        // 4位其他序列号(其中最后1位性别码:奇数男偶数女)
        int additionalCode = ThreadLocalRandom.current().nextInt(1000, 10000);

        // 组合前13位
        String partialID = century + birthDate + governorate + additionalCode;

        // 1位校验码
        int sum = 0;
        for (char c : partialID.toCharArray()) {
            sum += Character.getNumericValue(c);
        }
        sum = (10 - (sum % 10)) % 10; // 确保结果为0-9

        return partialID + sum;
    }
}
